<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="zSqBt" id="zSqBt"><span data-lake-id="ua4e5f1af" id="ua4e5f1af">典型回答</span></h1>
  <p data-lake-id="udb0931af" id="udb0931af"><br></p>
  <p data-lake-id="uf613e491" id="uf613e491"><span data-lake-id="uddb1eecc" id="uddb1eecc">HashMap有扩容机制，就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数（size）超过临界值（threshold）时就会自动扩容。在HashMap中，threshold = loadFactor * capacity。</span></p>
  <p data-lake-id="u259e3ba7" id="u259e3ba7"><span data-lake-id="u1fd93856" id="u1fd93856">​</span><br></p>
  <p data-lake-id="u9b81168e" id="u9b81168e"><span data-lake-id="ub51cb401" id="ub51cb401">​</span><br></p>
  <p data-lake-id="u0e484a67" id="u0e484a67"><span data-lake-id="u262b0564" id="u262b0564">所以，如果我们没有设置初始容量大小，随着元素的不断增加，HashMap会发生多次扩容，而HashMap中的扩容机制决定了每次扩容都需要重建hash表，是非常影响性能的。</span></p>
  <p data-lake-id="u4b8ed2e9" id="u4b8ed2e9"><span data-lake-id="u77f0eda3" id="u77f0eda3">​</span><br></p>
  <p data-lake-id="u53ffe627" id="u53ffe627" style="text-align: justify"><span data-lake-id="u43e66e7e" id="u43e66e7e" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">所以，首先可以明确的是，我们建议开发者在创建HashMap的时候指定初始化容量。并且《阿里巴巴开发手册》中也是这么建议的：</span></p>
  <p data-lake-id="u55b06a41" id="u55b06a41" style="text-align: justify"><span data-lake-id="uc53438dc" id="uc53438dc" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">​</span><br></p>
  <p data-lake-id="u99cf0ea1" id="u99cf0ea1" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2024/jpeg/5378072/1705732262005-310d6437-232e-47dd-9470-d9583d5da95f.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_19%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u3b3e9efc" id="u3b3e9efc" style="text-align: justify"><span data-lake-id="u72a7ac93" id="u72a7ac93"><br></span><span data-lake-id="u37e09546" id="u37e09546" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">那么，既然建议我们集合初始化的时候，要指定初始值大小，那么我们创建HashMap的时候，到底指定多少合适呢？</span></p>
  <p data-lake-id="u93f6c2df" id="u93f6c2df" style="text-align: justify"><span data-lake-id="u30924f52" id="u30924f52" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">​</span><br></p>
  <p data-lake-id="uffd9f2d4" id="uffd9f2d4" style="text-align: justify"><span data-lake-id="uedb9e970" id="uedb9e970">有些人会自然想到，我准备塞多少个元素我就设置成多少呗。比如我准备塞7个元素，那就new HashMap(7)。</span></p>
  <p data-lake-id="ubee98c87" id="ubee98c87" style="text-align: justify"><span data-lake-id="u2b72bda3" id="u2b72bda3" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">​</span><br></p>
  <p data-lake-id="u0a9cbeb1" id="u0a9cbeb1"><strong><span data-lake-id="u716dfb74" id="u716dfb74">但是，这么做不仅不对，而且以上方式创建出来的Map的容量也不是7。</span></strong></p>
  <p data-lake-id="u0cc03aac" id="u0cc03aac"><span data-lake-id="u03122313" id="u03122313">​</span><br></p>
  <p data-lake-id="u82021468" id="u82021468"><span data-lake-id="u7f107552" id="u7f107552">因为，当我们使用HashMap(int initialCapacity)来初始化容量的时候，HashMap并不会使用我们传进来的initialCapacity直接作为初识容量。</span></p>
  <p data-lake-id="u7bdfbbaf" id="u7bdfbbaf"><span data-lake-id="u30a87c77" id="u30a87c77">​</span><br></p>
  <p data-lake-id="u012ac36f" id="u012ac36f"><strong><span data-lake-id="u697b5b26" id="u697b5b26">JDK会默认帮我们计算一个相对合理的值当做初始容量。所谓合理值，其实是找到第一个比用户传入的值大的2的幂。</span></strong></p>
  <p data-lake-id="uacbb5a24" id="uacbb5a24"><span data-lake-id="u9d27794d" id="u9d27794d">​</span><br></p>
  <p data-lake-id="ud36b65e0" id="ud36b65e0"><span data-lake-id="uce366a0e" id="uce366a0e">也就是说，当我们new HashMap(7)创建HashMap的时候，JDK会通过计算，帮我们创建一个容量为8的Map；当我们new HashMap(9)创建HashMap的时候，JDK会通过计算，帮我们创建一个容量为16的Map。</span></p>
  <p data-lake-id="ue2e39938" id="ue2e39938"><span data-lake-id="ub91ebeea" id="ub91ebeea">​</span><br></p>
  <p data-lake-id="u16227b0e" id="u16227b0e"><strong><span data-lake-id="uc0d8246f" id="uc0d8246f">但是，这个值看似合理，实际上并不尽然。因为HashMap在根据用户传入的capacity计算得到的默认容量，并没有考虑到loadFactor这个因素，只是简单机械的计算出第一个大约这个数字的2的幂。</span></strong></p>
  <p data-lake-id="uaacfeeaf" id="uaacfeeaf"><span data-lake-id="u8cfa09f7" id="u8cfa09f7">​</span><br></p>
  <p data-lake-id="u0e36aafe" id="u0e36aafe"><span data-lake-id="u25039eff" id="u25039eff">loadFactor是负载因子，当HashMap中的元素个数（size）超过 threshold = loadFactor * capacity时，就会进行扩容。</span></p>
  <p data-lake-id="ufc5d5cdb" id="ufc5d5cdb"><span data-lake-id="u8b3e36fb" id="u8b3e36fb">​</span><br></p>
  <p data-lake-id="u3b9514aa" id="u3b9514aa"><span data-lake-id="u907508d5" id="u907508d5">也就是说，如果我们设置的默认值是7，经过JDK处理之后，HashMap的容量会被设置成8，但是，这个HashMap在元素个数达到 8*0.75 = 6的时候就会进行一次扩容，这明显是我们不希望见到的。</span></p>
  <p data-lake-id="u68f0c1a8" id="u68f0c1a8"><span data-lake-id="u2426cd68" id="u2426cd68">​</span><br></p>
  <p data-lake-id="uf5ef8291" id="uf5ef8291"><span data-lake-id="u4257ee13" id="u4257ee13">那么，到底设置成什么值比较合理呢？</span></p>
  <p data-lake-id="ufa324cd3" id="ufa324cd3"><span data-lake-id="udf2975fe" id="udf2975fe">​</span><br></p>
  <p data-lake-id="u5447e303" id="u5447e303"><span data-lake-id="ua16aa1cb" id="ua16aa1cb">这里我们可以参考JDK8中putAll方法中的实现的，这个实现在guava（21.0版本）也被采用。并且在阿里巴巴Java开发手册中也有这样的规定</span></p>
  <p data-lake-id="u198310f2" id="u198310f2"><span data-lake-id="uec2c734a" id="uec2c734a">​</span><br></p>
  <p data-lake-id="ua0326a20" id="ua0326a20"><img src="https://cdn.nlark.com/yuque/0/2022/png/719664/1668845250399-62fa1924-5491-4e83-9643-c92179ff3c6f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_26%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="uea5d4020" id="uea5d4020"><br></p>
  <p data-lake-id="ub8ef0255" id="ub8ef0255"><span data-lake-id="u55eedbc4" id="u55eedbc4">即：</span><code data-lake-id="u9f15dccc" id="u9f15dccc"><strong><span data-lake-id="ued02d5f9" id="ued02d5f9" style="color: #DF2A3F">return (int) ((float) expectedSize / 0.75F + 1.0F);</span></strong></code></p>
  <p data-lake-id="ubda3430c" id="ubda3430c"><br></p>
  <p data-lake-id="ueb0370a0" id="ueb0370a0"><span data-lake-id="u0ba1da10" id="u0ba1da10">比如我们计划向HashMap中放入7个元素的时候，我们通过expectedSize / 0.75F + 1.0F计算，7/0.75 + 1 = 10 ,10经过JDK处理之后，会被设置成16，这就大大的减少了扩容的几率。</span></p>
  <p data-lake-id="u2c6da618" id="u2c6da618"><span data-lake-id="u1405a087" id="u1405a087">​</span><br></p>
  <blockquote data-lake-id="uc0fa309c" id="uc0fa309c">
   <p data-lake-id="u16d686a7" id="u16d686a7"><span data-lake-id="u44d3f516" id="u44d3f516">当HashMap内部维护的哈希表的容量达到75%时（默认情况下），会触发rehash，而rehash的过程是比较耗费时间的。所以初始化容量要设置成expectedSize/0.75 + 1的话，可以有效的减少冲突也可以减小误差。（大家结合这个公式，好好理解下这句话）</span></p>
  </blockquote>
  <p data-lake-id="ud13dbcb5" id="ud13dbcb5"><strong><span data-lake-id="ufd68dc10" id="ufd68dc10">​</span></strong><br></p>
  <p data-lake-id="u63eada97" id="u63eada97"><strong><span data-lake-id="uf2f59571" id="uf2f59571">所以，我们可以认为，当我们明确知道HashMap中元素的个数的时候，把默认容量设置成expectedSize / 0.75F + 1.0F 是一个在性能上相对好的选择，但是，同时也会牺牲些内存。</span></strong></p>
  <p data-lake-id="ue38c4b5e" id="ue38c4b5e"><strong><span data-lake-id="u07e86a4b" id="u07e86a4b">​</span></strong><br></p>
  <p data-lake-id="ube92468a" id="ube92468a"><span data-lake-id="u11dfbe65" id="u11dfbe65" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">这个算法在guava中有实现，开发的时候，可以直接通过Maps类创建一个HashMap：</span></p>
  <p data-lake-id="u90961ec8" id="u90961ec8"><span data-lake-id="u1d0886fd" id="u1d0886fd" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">​</span><br></p>
  <pre lang="java"><code>
Map&lt;String, String&gt; map = Maps.newHashMapWithExpectedSize(7);
</code></pre>
  <p data-lake-id="u058fa3e7" id="u058fa3e7"><br></p>
  <p data-lake-id="u78dadad2" id="u78dadad2"><span data-lake-id="u9fa71c8d" id="u9fa71c8d" class="lake-fontsize-12">其代码实现如下：</span></p>
  <p data-lake-id="uf5a21d29" id="uf5a21d29"><span data-lake-id="ua5fae753" id="ua5fae753" class="lake-fontsize-12">​</span><br></p>
  <pre lang="java"><code>
public static &lt;K, V&gt; HashMap&lt;K, V&gt; newHashMapWithExpectedSize(int expectedSize) {
    return new HashMap(capacity(expectedSize));
}

static int capacity(int expectedSize) {
    if (expectedSize &lt; 3) {
        CollectPreconditions.checkNonnegative(expectedSize, "expectedSize");
        return expectedSize + 1;
    } else {
        return expectedSize &lt; 1073741824 ? (int)((float)expectedSize / 0.75F + 1.0F) : 2147483647;
    }
}
</code></pre>
  <p data-lake-id="u1d17ac55" id="u1d17ac55"><br></p>
  <p data-lake-id="udf1a11f9" id="udf1a11f9"><span data-lake-id="u8398616b" id="u8398616b">但是，以上的操作是一种用内存换性能的做法，真正使用的时候，要考虑到内存的影响。但是，大多数情况下，我们还是认为内存是一种比较富裕的资源。</span></p>
  <p data-lake-id="u0a7e8829" id="u0a7e8829"><span data-lake-id="u5843be06" id="u5843be06">​</span><br></p>
  <p data-lake-id="uf0439c50" id="uf0439c50"><span data-lake-id="u1cbbcc81" id="u1cbbcc81">但是话又说回来了，有些时候，我们到底要不要设置HashMap的初识值，这个值又设置成多少，真的有那么大影响吗？其实也不见得！</span></p>
  <p data-lake-id="u10be8f39" id="u10be8f39"><span data-lake-id="uf6720b5a" id="uf6720b5a">​</span><br></p>
  <p data-lake-id="u098c2790" id="u098c2790"><span data-lake-id="ubf258fbf" id="ubf258fbf">可是，大的性能优化，不就是一个一个的优化细节堆叠出来的吗？</span></p>
  <p data-lake-id="u0f536a39" id="u0f536a39"><span data-lake-id="udc821554" id="udc821554">​</span><br></p>
  <p data-lake-id="u32f0b493" id="u32f0b493"><span data-lake-id="ua5a7f5cb" id="ua5a7f5cb">再不济，以后你写代码的时候，使用Maps.newHashMapWithExpectedSize(7);的写法，也可以让同事和老板眼前一亮。</span></p>
  <p data-lake-id="u29b2e3b7" id="u29b2e3b7"><br></p>
 </body>
</html>